package net.maergaiyun.aspect;


import lombok.extern.slf4j.Slf4j;
import net.maergaiyun.annotation.RepeatSubmit;
import net.maergaiyun.constant.RedisKey;
import net.maergaiyun.enums.BizCodeEnum;
import net.maergaiyun.exception.BizException;
import net.maergaiyun.interceptor.LoginInterceptor;
import net.maergaiyun.util.CommonUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 定义一个切面类
 */
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 定义@Pointcut注解表达式
     * 方式一: @annotation: 当执行的方法上拥有指定的注解时生效
     * 方式二: execution: 一般用于指定方法的执行
     *
     * @param repeatSubmit
     */
    @Pointcut("@annotation(repeatSubmit)")
    public void pointCutNoRepeatSubmit(RepeatSubmit repeatSubmit) {

    }

    /**
     * 环绕通知, 围绕着方法执行
     *
     * @param joinPoint
     * @param repeatSubmit
     * @return
     * @Around 可以用来在调用一个具体方法前后完成具体任务
     * <p>
     * 方式一: 单用@Around("execution(* net.maergaiyun.controller.*.*(..))")
     * 方式二: 用@Pointcut和@Around联合注解也可以
     */
    @Around("pointCutNoRepeatSubmit(repeatSubmit)")
    public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

        boolean res = false;

        String type = repeatSubmit.limitType().name();
        if (type.equalsIgnoreCase(RepeatSubmit.Type.PARAM.name())) {
            // 方式一: 参数形式防重提交
            long lockTime = repeatSubmit.lockTime();
            String ipAddr = CommonUtil.getIpAddr(request);
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Method method = methodSignature.getMethod();
            String className = method.getDeclaringClass().getName();
            String key = String.format("%s-%s-%s-%s", ipAddr, className, method, accountNo);

//            res = redisTemplate.opsForValue().setIfAbsent(key, "1", lockTime, TimeUnit.SECONDS);
            RLock lock = redissonClient.getLock(key);
            // 尝试加锁, 最多等待0秒, 上锁之后5秒解锁
            res = lock.tryLock(0, lockTime, TimeUnit.SECONDS);

        } else {
            // 方式二: 令牌形式防重提交
            String requestToken = request.getHeader("request-token");
            if (StringUtils.isBlank(requestToken)) {
                throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
            }
            String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, accountNo, requestToken);
            /**
             * 提交表单的token key
             * 删除成功即完成
             */
            res = redisTemplate.delete(key);
        }
        if (!res) {
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_REPEAT);
        }
        log.info("环绕通知执行前");
        Object obj = joinPoint.proceed();
        log.info("环绕通知执行后");
        return obj;
    }
}
